Project Description:

This markdown outlines the process of creating string line charts to represent scheduled trips for each trolley route, in each direction and for each available Fall/Spring season for which GTFS static data was published by SEPTA.

The code also exports the string line charts into folders for each season.

The GTFS files can be found at SEPTA’s GitHub Page

Code Setup

Install and load packages:

# List of required packages
required_packages <- c("tidyverse", "tidytransit", "lubridate", "here", "hms")

# Function to install and load packages if not already installed
install_and_load <- function(packages) {
  for (pkg in packages) {
    if (!requireNamespace(pkg, quietly = TRUE)) {
      install.packages(pkg)
    }
    library(pkg, character.only = TRUE)
  }
}

# Install and load all required packages
install_and_load(required_packages)
## Warning: package 'tidyverse' was built under R version 4.3.3
## Warning: package 'ggplot2' was built under R version 4.3.3
## Warning: package 'tibble' was built under R version 4.3.3
## Warning: package 'tidyr' was built under R version 4.3.3
## Warning: package 'readr' was built under R version 4.3.3
## Warning: package 'purrr' was built under R version 4.3.3
## Warning: package 'dplyr' was built under R version 4.3.3
## Warning: package 'stringr' was built under R version 4.3.3
## Warning: package 'forcats' was built under R version 4.3.3
## Warning: package 'lubridate' was built under R version 4.3.3
## Warning: package 'tidytransit' was built under R version 4.3.3
## Warning: package 'here' was built under R version 4.3.3
## Warning: package 'hms' was built under R version 4.3.3

Instructions:

Create a folder named “GTFS Data” and store required GTFS bus files here. This code will create a folder by the same name if it does not already exist:

gtfs_data_folder <- here("GTFS Data")

if (!dir.exists(gtfs_data_folder)) {
  dir.create(gtfs_data_folder)
  message("Folder 'GTFS Data' created.")
} else {
  message("Folder 'GTFS Data' already exists.")
}
## Folder 'GTFS Data' already exists.

Select the years and seasons for which string line charts are to be generated:

years <- 2016:2024
seasons <- c("Fall", "Spring")

season_list <- expand.grid(season = seasons, year = years) %>%
  arrange(year, season) %>%
  mutate(season_name = paste(season, year))

Select the routes for which string line charts are to be generated:

routes_list <- c("10", "11", "13", "15", "34", "36")

A test string line chart outlining each step in the process:

Methodology:

  1. Load the GTFS Data
  2. Create functions to better process time variables
  3. Filter the Stop Times table by Route and Direction.
  4. Filter the Stop Times table by the most appropriate Service ID that represents Trip IDs covering the entire day and occours the most number of times in a season. 5)Represent each trip ID as a string line over the course of a day, colour coded by time periods.

The following steps break down each process to create the string line charts:

Load GTFS Data from the “GTFS Data” folder

gtfs_data_spring_2024 <- read_gtfs(here("GTFS Data", "google_bus_Spring 2024.zip"))

Create functions for processing time:

Function to convert time strings to seconds:

time_to_seconds <- function(time_string) {
  if (!is.na(time_string)) {
    hms <- strsplit(time_string, ":")[[1]]
    as.numeric(hms[1]) * 3600 + as.numeric(hms[2]) * 60 + as.numeric(hms[3])
  } else {
    NA_real_  # Return NA as a numeric value
  }
}

Function to calculate time periods based on the first digit of the column

time_periods <- function(numerical) {
  case_when(
    between(numerical, 0, 6.99) ~ "00.00 - 07.00 - Early Morning",
    between(numerical, 7, 8.99) ~ "07.00 - 09.00 - AM Rush",
    between(numerical, 9, 16.99) ~ "09.00 - 17.00 - Midday",
    between(numerical, 17, 19.99) ~ "17.00 - 20.00 - PM Rush",
    between(numerical, 20, 23.99) ~ "20.00 - 24.00 - Night"
  )
}

Create a test string line chart for one route, one direction and one season:

Save individual GTFS tables:

routes <- gtfs_data_spring_2024$routes
trips <- gtfs_data_spring_2024$trips
stop_times <- gtfs_data_spring_2024$stop_times
calendar_dates <- gtfs_data_spring_2024$calendar_dates
stops <- gtfs_data_spring_2024$stops

Filter Route 10:

trips_data <- trips %>%
  filter(route_id == "10" & !trip_headsign == "40th-Market")   

Get unique directions for the route, excluding “40th-Market”

directions <- trips_data %>%
    filter(trip_headsign != "40th-Market") %>%
    pull(trip_headsign) %>%
    unique()

Filter trips in one direction by choosing one Trip Headsign

trips_direction <- trips_data %>%
      filter(trip_headsign == "13th-Market")

Filter Service IDs for weekday trips

weekday_service_ids <- calendar_dates %>%
  mutate(weekday = wday(date, label = TRUE)) %>%  # Add a column for the day of the week
  filter(!weekday %in% c("Sun", "Sat")) %>%       # Exclude weekends
  pull(service_id) 

Find common Service IDs that exists in both trips_data and calendar_dates

common_service_ids <- weekday_service_ids[weekday_service_ids %in% trips_data$service_id]

Filter calendar dates with only the common Service IDs and remove dates where services were excluded (exception_type = 2)

filtered_calendar_dates <- calendar_dates %>%
      filter(service_id %in% common_service_ids)

Join the filtered calendar dates with trips on the selected route and direction

trips_dates <- right_join(trips_direction, filtered_calendar_dates, by = "service_id") %>%
      filter(!is.na(trip_id))

Arrange Service IDs by most to least run patterns across the filtered calendar dates

trips_per_service_id <- trips_dates %>%
      group_by(date, service_id) %>%
      count(date) %>%
      arrange(desc(n))

Extract the Service ID with the most patterns

best_service_id <- trips_per_service_id[1,] %>%   # Sort by count in descending order
      pull(service_id)

Filter all trips that run on the selected Service ID

trips_selected <- trips_direction %>%
    filter(service_id == best_service_id)

Filter the stop times corresponding to the selected patterns and convert time variables to more usable formats

trip_stop_times <- stop_times %>%
    filter(trip_id %in% trips_selected$trip_id) %>%
    mutate(
      departure_time = as.character(departure_time),
      departure_seconds = sapply(departure_time, time_to_seconds),
      departure_hours = departure_seconds / 3600,
      time_periods = time_periods(departure_hours),
      arrival_time = as.POSIXct(arrival_time, format = "%H:%M:%S", tz = "UTC")
    ) %>%
    arrange(trip_id, stop_sequence)

Plot the String Line Chart for one route and direction

ggplot(trip_stop_times, aes(x = arrival_time, y = stop_sequence, color = time_periods, group = trip_id)) +
        geom_line() +
        labs(
          title = paste("String Line Chart for Route", "10", "13th-Market", "on Weekdays", "in Spring 2024"),
          x = "Time",
          y = "Stop Sequence"
        ) +
        scale_x_datetime(date_labels = "%H:%M") +
        theme_minimal()

Create a function to create string lines for each trolley route and direction for a single season.

Methodology: Run for loops that cycle through each Route and Direction

create_string_line_chart <- function(gtfs_data_spring_2024, routes_list) {
  routes <- gtfs_data_spring_2024$routes
  trips <- gtfs_data_spring_2024$trips
  stop_times <- gtfs_data_spring_2024$stop_times
  calendar_dates <- gtfs_data_spring_2024$calendar_dates
  stops <- gtfs_data_spring_2024$stops
  
  for (route in routes_list) {
    # Filter the routes table to find the correct route_id
    route_data <- routes %>%
      filter(route_id == route)
    
    if (nrow(route_data) == 0) {
      message(paste("Route", route, "not found in GTFS data. Skipping..."))
      next
    }
    
    trips_data <- trips %>%
      filter(route_id == route_data$route_id)
    
    # Get unique directions for the route, excluding "40th-Market"
    directions <- trips_data %>%
      filter(trip_headsign != "40th-Market") %>%
      pull(trip_headsign) %>%
      unique()
    
    for (direction in directions) {
      trips_direction <- trips_data %>%
        filter(trip_headsign == direction)
      
      weekday_service_ids <- calendar_dates %>%
        mutate(weekday = wday(date, label = TRUE)) %>%  # Add a column for the day of the week
        filter(!weekday %in% c("Sun", "Sat")) %>%       # Exclude weekends
        pull(service_id)                                # Extract the service_id values
      
      # Find common service_ids that exists in both trips_data and calendar_dates
      common_service_ids <- weekday_service_ids[weekday_service_ids %in% trips_direction$service_id]
      
      if (length(common_service_ids) == 0) {
        message(paste("No common service_id found for route", route, "and direction", direction, ". Skipping..."))
        next
      }
      
      filtered_calendar_dates <- calendar_dates %>%
        filter(service_id %in% common_service_ids)
      
      trips_dates <- right_join(trips_direction, filtered_calendar_dates, by = "service_id") %>%
        filter(!is.na(trip_id))
      
      trips_per_service_id <- trips_dates %>%
        group_by(date, service_id) %>%
        count(date) %>%
        arrange(desc(n))
      
      best_service_id <- trips_per_service_id[1, ] %>%
        pull(service_id)
      
      trips_on_date <- trips_direction %>%
        filter(service_id == best_service_id)
      
      trip_stop_times <- stop_times %>%
        filter(trip_id %in% trips_on_date$trip_id) %>%
        mutate(
          departure_time = as.character(departure_time),
          departure_seconds = sapply(departure_time, time_to_seconds),
          departure_hours = departure_seconds / 3600,
          time_periods = time_periods(departure_hours),
          arrival_time = as.POSIXct(arrival_time, format = "%H:%M:%S", tz = "UTC")
        ) %>%
        arrange(trip_id, stop_sequence)
      
      trip_stop_times_with_names <- trip_stop_times %>%
        left_join(stops, by = "stop_id") %>%
        mutate(stop_name_number = paste(stop_sequence, stop_name, sep = "_"))
      
      string_chart <- ggplot(trip_stop_times_with_names, aes(x = arrival_time, y = stop_sequence, color = time_periods, group = trip_id)) +
        geom_line() +
        labs(
          title = paste("String Line Chart for Route", route, " Trips to", direction, "on Weekdays", "in Spring 2024"),
          x = "Time",
          y = "Stop Sequence"
        ) +
        scale_x_datetime(date_labels = "%H:%M") +
        theme_minimal()
     output_path <- here("String Lines Exports", paste0("gtfs_route_", route, "_", gsub(" ", "_", direction), "_Weekday_string_line_chart.jpg"))
      ggsave(output_path, plot = string_chart, width = 4000, height = 1750, units = "px")
      print(string_chart)
    }
  }
}

Run the function to create string lines for routes 10, 11, 13, 15, 34, 36

create_string_line_chart(gtfs_data_spring_2024, c("10", "11", "13", "15", "34", "36"))

Export string lines for all available seasons:

Methodology: Run for loops that cycle through each Route, Direction and Season

Create a function to create string lines for all available seasons:

create_string_line_chart_seasons <- function(gtfs_data, routes_list, season) {
  routes <- gtfs_data$routes
  trips <- gtfs_data$trips
  stop_times <- gtfs_data$stop_times
  calendar_dates <- gtfs_data$calendar_dates
  stops <- gtfs_data$stops
  
  for (route in routes_list) {
    # Filter the routes table to find the correct route_id
    route_data <- routes %>%
      filter(route_id == route)
    
    if (nrow(route_data) == 0) {
      message(paste("Route", route, "not found in GTFS data. Skipping..."))
      next
    }
    
    trips_data <- trips %>%
      filter(route_id == route_data$route_id)
    
    # Get unique directions for the route, excluding "40th-Market"
    directions <- trips_data %>%
      filter(trip_headsign != "40th-Market") %>%
      pull(trip_headsign) %>%
      unique()
    
    for (direction in directions) {
      trips_direction <- trips_data %>%
        filter(trip_headsign == direction)
      
      weekday_service_ids <- calendar_dates %>%
        mutate(weekday = wday(date, label = TRUE)) %>%  # Add a column for the day of the week
        filter(!weekday %in% c("Sun", "Sat")) %>%       # Exclude weekends
        pull(service_id)                                # Extract the service_id values
      
      # Find common service_ids that exist in both trips_data and calendar_dates
      common_service_ids <- weekday_service_ids[weekday_service_ids %in% trips_direction$service_id]
      
      if (length(common_service_ids) == 0) {
        message(paste("No common service_id found for route", route, "and direction", direction, ". Skipping..."))
        next
      }
      
      filtered_calendar_dates <- calendar_dates %>%
        filter(service_id %in% common_service_ids)
      
      trips_dates <- right_join(trips_direction, filtered_calendar_dates, by = "service_id") %>%
        filter(!is.na(trip_id))
      
      trips_per_service_id <- trips_dates %>%
        group_by(date, service_id) %>%
        count(date) %>%
        arrange(desc(n))
      
      best_service_id <- trips_per_service_id[1, ] %>%
        pull(service_id)
      
      trips_on_date <- trips_direction %>%
        filter(service_id == best_service_id)
      
      trip_stop_times <- stop_times %>%
        filter(trip_id %in% trips_on_date$trip_id) %>%
        mutate(
          departure_time = as.character(departure_time),
          departure_seconds = sapply(departure_time, time_to_seconds),
          departure_hours = departure_seconds / 3600,
          time_periods = time_periods(departure_hours),
          arrival_time = as.POSIXct(arrival_time, format = "%H:%M:%S", tz = "UTC")
        ) %>%
        arrange(trip_id, stop_sequence)
      
      trip_stop_times_with_names <- trip_stop_times %>%
        left_join(stops, by = "stop_id") %>%
        mutate(stop_name_number = paste(stop_sequence, stop_name, sep = "_"))
      
      string_chart <- ggplot(trip_stop_times_with_names, aes(x = arrival_time, y = stop_sequence, color = time_periods, group = trip_id)) +
        geom_line() +
        labs(
          title = paste("String Line Chart for Route", route, "Trips to", direction, "on Weekdays", season),
          x = "Time",
          y = "Stop Sequence"
        ) +
        scale_x_datetime(date_labels = "%H:%M") +
        theme_minimal()
      
      # Create the season-specific folder inside "String Lines Exports"
      season_folder <- here("String Lines Exports", season)
      if (!dir.exists(season_folder)) {
        dir.create(season_folder, recursive = TRUE)
      }
      
      output_path <- file.path(season_folder, paste0("gtfs_route_", route, "_", gsub(" ", "_", direction), "_Weekday_string_line_chart.jpg"))
      ggsave(output_path, plot = string_chart, width = 4000, height = 1750, units = "px")
      print(string_chart)
    }
  }
}

Loop over each season and create string line charts for each route, and save in their respective folders:

for (season in season_list$season_name) {
  gtfs_file <- here("GTFS Data", paste0("google_bus_", season, ".zip"))
  
  if (!file.exists(gtfs_file)) {
    message(paste("GTFS file for", season, "not found. Skipping..."))
    next
  }
  
  gtfs_data <- read_gtfs(gtfs_file)
  
  create_string_line_chart_seasons(gtfs_data, routes_list, season)
}
## Route 10 not found in GTFS data. Skipping...
## Route 11 not found in GTFS data. Skipping...
## Route 13 not found in GTFS data. Skipping...
## Route 15 not found in GTFS data. Skipping...
## Route 34 not found in GTFS data. Skipping...
## Route 36 not found in GTFS data. Skipping...
## Route 10 not found in GTFS data. Skipping...
## Route 11 not found in GTFS data. Skipping...
## Route 13 not found in GTFS data. Skipping...
## Route 15 not found in GTFS data. Skipping...
## Route 34 not found in GTFS data. Skipping...
## Route 36 not found in GTFS data. Skipping...
## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Route 10 not found in GTFS data. Skipping...
## Route 11 not found in GTFS data. Skipping...
## Route 13 not found in GTFS data. Skipping...
## Route 15 not found in GTFS data. Skipping...
## Route 34 not found in GTFS data. Skipping...
## Route 36 not found in GTFS data. Skipping...
## GTFS file for Fall 2018 not found. Skipping...

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## No common service_id found for route 10 and direction 33rd-Market . Skipping...
## No common service_id found for route 10 and direction 63rd-Malvern . Skipping...
## No common service_id found for route 11 and direction 13th-Market . Skipping...
## No common service_id found for route 11 and direction Darby Transportation Center . Skipping...
## No common service_id found for route 13 and direction 13th-Market . Skipping...
## No common service_id found for route 13 and direction Darby Transportation Center . Skipping...
## No common service_id found for route 13 and direction Yeadon . Skipping...
## No common service_id found for route 15 and direction 63rd-Girard . Skipping...
## No common service_id found for route 15 and direction Richmond-Westmoreland . Skipping...
## No common service_id found for route 34 and direction 13th-Market . Skipping...
## No common service_id found for route 34 and direction 61st-Baltimore . Skipping...
## No common service_id found for route 34 and direction Woodland-Island . Skipping...
## No common service_id found for route 34 and direction 73rd-Elmwood . Skipping...
## No common service_id found for route 36 and direction 13th-Market . Skipping...
## No common service_id found for route 36 and direction Eastwick . Skipping...
## No common service_id found for route 36 and direction Elmwood-Island . Skipping...

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Route 36 not found in GTFS data. Skipping...
## No common service_id found for route 10 and direction 13th-Market . Skipping...
## No common service_id found for route 10 and direction 63rd-Malvern . Skipping...
## No common service_id found for route 11 and direction 13th-Market . Skipping...
## No common service_id found for route 11 and direction Darby Transportation Center . Skipping...
## No common service_id found for route 13 and direction 13th-Market . Skipping...
## No common service_id found for route 13 and direction Yeadon . Skipping...
## No common service_id found for route 13 and direction Darby Transportation Center . Skipping...
## No common service_id found for route 15 and direction 63rd-Girard . Skipping...
## No common service_id found for route 15 and direction Richmond-Westmoreland . Skipping...
## No common service_id found for route 34 and direction 13th-Market . Skipping...
## No common service_id found for route 34 and direction 61st-Baltimore . Skipping...
## No common service_id found for route 34 and direction 73rd-Elmwood . Skipping...
## No common service_id found for route 34 and direction Woodland-Island . Skipping...
## No common service_id found for route 36 and direction 13th-Market . Skipping...
## No common service_id found for route 36 and direction Eastwick . Skipping...
## No common service_id found for route 36 and direction Elmwood-Island . Skipping...
## No common service_id found for route 10 and direction 33rd-Market . Skipping...
## No common service_id found for route 10 and direction 63rd-Malvern . Skipping...
## No common service_id found for route 11 and direction 13th-Market . Skipping...
## No common service_id found for route 11 and direction Darby Transportation Center . Skipping...
## No common service_id found for route 13 and direction 13th-Market . Skipping...
## No common service_id found for route 13 and direction Darby Transportation Center . Skipping...
## No common service_id found for route 13 and direction Yeadon . Skipping...
## No common service_id found for route 15 and direction 63rd-Girard . Skipping...
## No common service_id found for route 15 and direction Richmond-Westmoreland . Skipping...
## No common service_id found for route 34 and direction 13th-Market . Skipping...
## No common service_id found for route 34 and direction 61st-Baltimore . Skipping...
## No common service_id found for route 34 and direction Woodland-Island . Skipping...
## No common service_id found for route 34 and direction 73rd-Elmwood . Skipping...
## No common service_id found for route 36 and direction 13th-Market . Skipping...
## No common service_id found for route 36 and direction Eastwick . Skipping...
## No common service_id found for route 36 and direction Elmwood-Island . Skipping...

## Route 11 not found in GTFS data. Skipping...

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## GTFS file for Fall 2024 not found. Skipping...
## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 2 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 122 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 140 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 2 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 5 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 19 of `x` matches multiple rows in `y`.
## ℹ Row 75 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 117 of `x` matches multiple rows in `y`.
## ℹ Row 69 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 117 of `x` matches multiple rows in `y`.
## ℹ Row 75 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 2 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

## Warning in right_join(trips_direction, filtered_calendar_dates, by = "service_id"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 5 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

LS0tDQp0aXRsZTogIkdURlMgVHJvbGxleSBTdHJpbmcgTGluZXMiDQphdXRob3I6IFZhcnVuIEJoYWtocmkNCmRhdGU6ICIyMDI0LTA4LTE1Ig0Kb3V0cHV0OiANCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2RlX2ZvbGRpbmc6ICJoaWRlIg0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICBvdXRwdXRfZGlyOiBoZXJlKCJNYXJrZG93bnMgT3V0cHV0IikNCi0tLQ0KDQojIyMgUHJvamVjdCBEZXNjcmlwdGlvbjoNCg0KVGhpcyBtYXJrZG93biBvdXRsaW5lcyB0aGUgcHJvY2VzcyBvZiBjcmVhdGluZyBzdHJpbmcgbGluZSBjaGFydHMgdG8gcmVwcmVzZW50IHNjaGVkdWxlZCB0cmlwcyBmb3IgZWFjaCB0cm9sbGV5IHJvdXRlLCBpbiBlYWNoIGRpcmVjdGlvbiBhbmQgZm9yIGVhY2ggYXZhaWxhYmxlIEZhbGwvU3ByaW5nIHNlYXNvbiBmb3Igd2hpY2ggR1RGUyBzdGF0aWMgZGF0YSB3YXMgcHVibGlzaGVkIGJ5IFNFUFRBLiANCg0KVGhlIGNvZGUgYWxzbyBleHBvcnRzIHRoZSBzdHJpbmcgbGluZSBjaGFydHMgaW50byBmb2xkZXJzIGZvciBlYWNoIHNlYXNvbi4NCg0KVGhlIEdURlMgZmlsZXMgY2FuIGJlIGZvdW5kIGF0IFtTRVBUQSdzIEdpdEh1YiBQYWdlXShodHRwczovL2dpdGh1Yi5jb20vc2VwdGFkZXYvR1RGUy90YWdzKQ0KDQojIyMgQ29kZSBTZXR1cA0KDQpJbnN0YWxsIGFuZCBsb2FkIHBhY2thZ2VzOg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1UUlVFLCByZXN1bHRzID0gJ2hpZGUnfQ0KDQojIExpc3Qgb2YgcmVxdWlyZWQgcGFja2FnZXMNCnJlcXVpcmVkX3BhY2thZ2VzIDwtIGMoInRpZHl2ZXJzZSIsICJ0aWR5dHJhbnNpdCIsICJsdWJyaWRhdGUiLCAiaGVyZSIsICJobXMiKQ0KDQojIEZ1bmN0aW9uIHRvIGluc3RhbGwgYW5kIGxvYWQgcGFja2FnZXMgaWYgbm90IGFscmVhZHkgaW5zdGFsbGVkDQppbnN0YWxsX2FuZF9sb2FkIDwtIGZ1bmN0aW9uKHBhY2thZ2VzKSB7DQogIGZvciAocGtnIGluIHBhY2thZ2VzKSB7DQogICAgaWYgKCFyZXF1aXJlTmFtZXNwYWNlKHBrZywgcXVpZXRseSA9IFRSVUUpKSB7DQogICAgICBpbnN0YWxsLnBhY2thZ2VzKHBrZykNCiAgICB9DQogICAgbGlicmFyeShwa2csIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkNCiAgfQ0KfQ0KDQojIEluc3RhbGwgYW5kIGxvYWQgYWxsIHJlcXVpcmVkIHBhY2thZ2VzDQppbnN0YWxsX2FuZF9sb2FkKHJlcXVpcmVkX3BhY2thZ2VzKQ0KDQpgYGANCg0KIyMjIEluc3RydWN0aW9uczoNCg0KQ3JlYXRlIGEgZm9sZGVyIG5hbWVkICJHVEZTIERhdGEiIGFuZCBzdG9yZSByZXF1aXJlZCBHVEZTIGJ1cyBmaWxlcyBoZXJlLiBUaGlzIGNvZGUgd2lsbCBjcmVhdGUgYSBmb2xkZXIgYnkgdGhlIHNhbWUgbmFtZSBpZiBpdCBkb2VzIG5vdCBhbHJlYWR5IGV4aXN0Og0KDQpgYGB7cn0NCmd0ZnNfZGF0YV9mb2xkZXIgPC0gaGVyZSgiR1RGUyBEYXRhIikNCg0KaWYgKCFkaXIuZXhpc3RzKGd0ZnNfZGF0YV9mb2xkZXIpKSB7DQogIGRpci5jcmVhdGUoZ3Rmc19kYXRhX2ZvbGRlcikNCiAgbWVzc2FnZSgiRm9sZGVyICdHVEZTIERhdGEnIGNyZWF0ZWQuIikNCn0gZWxzZSB7DQogIG1lc3NhZ2UoIkZvbGRlciAnR1RGUyBEYXRhJyBhbHJlYWR5IGV4aXN0cy4iKQ0KfQ0KDQpgYGANCg0KU2VsZWN0IHRoZSB5ZWFycyBhbmQgc2Vhc29ucyBmb3Igd2hpY2ggc3RyaW5nIGxpbmUgY2hhcnRzIGFyZSB0byBiZSBnZW5lcmF0ZWQ6DQoNCmBgYHtyfQ0KeWVhcnMgPC0gMjAxNjoyMDI0DQpzZWFzb25zIDwtIGMoIkZhbGwiLCAiU3ByaW5nIikNCg0Kc2Vhc29uX2xpc3QgPC0gZXhwYW5kLmdyaWQoc2Vhc29uID0gc2Vhc29ucywgeWVhciA9IHllYXJzKSAlPiUNCiAgYXJyYW5nZSh5ZWFyLCBzZWFzb24pICU+JQ0KICBtdXRhdGUoc2Vhc29uX25hbWUgPSBwYXN0ZShzZWFzb24sIHllYXIpKQ0KDQpgYGANCg0KU2VsZWN0IHRoZSByb3V0ZXMgZm9yIHdoaWNoIHN0cmluZyBsaW5lIGNoYXJ0cyBhcmUgdG8gYmUgZ2VuZXJhdGVkOg0KYGBge3J9DQpyb3V0ZXNfbGlzdCA8LSBjKCIxMCIsICIxMSIsICIxMyIsICIxNSIsICIzNCIsICIzNiIpDQpgYGANCg0KDQojIyMgQSB0ZXN0IHN0cmluZyBsaW5lIGNoYXJ0IG91dGxpbmluZyBlYWNoIHN0ZXAgaW4gdGhlIHByb2Nlc3M6DQoNCk1ldGhvZG9sb2d5Og0KDQoxKSBMb2FkIHRoZSBHVEZTIERhdGENCjIpIENyZWF0ZSBmdW5jdGlvbnMgdG8gYmV0dGVyIHByb2Nlc3MgdGltZSB2YXJpYWJsZXMNCjMpIEZpbHRlciB0aGUgU3RvcCBUaW1lcyB0YWJsZSBieSBSb3V0ZSBhbmQgRGlyZWN0aW9uLg0KNCkgRmlsdGVyIHRoZSBTdG9wIFRpbWVzIHRhYmxlIGJ5IHRoZSBtb3N0IGFwcHJvcHJpYXRlIFNlcnZpY2UgSUQgdGhhdCByZXByZXNlbnRzIFRyaXAgSURzIGNvdmVyaW5nIHRoZSBlbnRpcmUgZGF5IGFuZCBvY2NvdXJzIHRoZSBtb3N0IG51bWJlciBvZiB0aW1lcyBpbiBhIHNlYXNvbi4NCjUpUmVwcmVzZW50IGVhY2ggdHJpcCBJRCBhcyBhIHN0cmluZyBsaW5lIG92ZXIgdGhlIGNvdXJzZSBvZiBhIGRheSwgY29sb3VyIGNvZGVkIGJ5IHRpbWUgcGVyaW9kcy4NCg0KVGhlIGZvbGxvd2luZyBzdGVwcyBicmVhayBkb3duIGVhY2ggcHJvY2VzcyB0byBjcmVhdGUgdGhlIHN0cmluZyBsaW5lIGNoYXJ0czoNCg0KTG9hZCBHVEZTIERhdGEgZnJvbSB0aGUgIkdURlMgRGF0YSIgZm9sZGVyDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBjYWNoZT1UUlVFLCByZXN1bHRzID0gJ2hpZGUnfQ0KZ3Rmc19kYXRhX3NwcmluZ18yMDI0IDwtIHJlYWRfZ3RmcyhoZXJlKCJHVEZTIERhdGEiLCAiZ29vZ2xlX2J1c19TcHJpbmcgMjAyNC56aXAiKSkNCmBgYA0KIyMjIENyZWF0ZSBmdW5jdGlvbnMgZm9yIHByb2Nlc3NpbmcgdGltZToNCkZ1bmN0aW9uIHRvIGNvbnZlcnQgdGltZSBzdHJpbmdzIHRvIHNlY29uZHM6DQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBjYWNoZT1UUlVFLCByZXN1bHRzID0gJ2hpZGUnfSANCnRpbWVfdG9fc2Vjb25kcyA8LSBmdW5jdGlvbih0aW1lX3N0cmluZykgew0KICBpZiAoIWlzLm5hKHRpbWVfc3RyaW5nKSkgew0KICAgIGhtcyA8LSBzdHJzcGxpdCh0aW1lX3N0cmluZywgIjoiKVtbMV1dDQogICAgYXMubnVtZXJpYyhobXNbMV0pICogMzYwMCArIGFzLm51bWVyaWMoaG1zWzJdKSAqIDYwICsgYXMubnVtZXJpYyhobXNbM10pDQogIH0gZWxzZSB7DQogICAgTkFfcmVhbF8gICMgUmV0dXJuIE5BIGFzIGEgbnVtZXJpYyB2YWx1ZQ0KICB9DQp9DQpgYGANCkZ1bmN0aW9uIHRvIGNhbGN1bGF0ZSB0aW1lIHBlcmlvZHMgYmFzZWQgb24gdGhlIGZpcnN0IGRpZ2l0IG9mIHRoZSBjb2x1bW4NCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUUsIHJlc3VsdHMgPSAnaGlkZSd9DQp0aW1lX3BlcmlvZHMgPC0gZnVuY3Rpb24obnVtZXJpY2FsKSB7DQogIGNhc2Vfd2hlbigNCiAgICBiZXR3ZWVuKG51bWVyaWNhbCwgMCwgNi45OSkgfiAiMDAuMDAgLSAwNy4wMCAtIEVhcmx5IE1vcm5pbmciLA0KICAgIGJldHdlZW4obnVtZXJpY2FsLCA3LCA4Ljk5KSB+ICIwNy4wMCAtIDA5LjAwIC0gQU0gUnVzaCIsDQogICAgYmV0d2VlbihudW1lcmljYWwsIDksIDE2Ljk5KSB+ICIwOS4wMCAtIDE3LjAwIC0gTWlkZGF5IiwNCiAgICBiZXR3ZWVuKG51bWVyaWNhbCwgMTcsIDE5Ljk5KSB+ICIxNy4wMCAtIDIwLjAwIC0gUE0gUnVzaCIsDQogICAgYmV0d2VlbihudW1lcmljYWwsIDIwLCAyMy45OSkgfiAiMjAuMDAgLSAyNC4wMCAtIE5pZ2h0Ig0KICApDQp9DQpgYGANCiMjIyBDcmVhdGUgYSB0ZXN0IHN0cmluZyBsaW5lIGNoYXJ0IGZvciBvbmUgcm91dGUsIG9uZSBkaXJlY3Rpb24gYW5kIG9uZSBzZWFzb246DQoNCg0KU2F2ZSBpbmRpdmlkdWFsIEdURlMgdGFibGVzOg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGU9VFJVRSwgcmVzdWx0cyA9ICdoaWRlJ30NCnJvdXRlcyA8LSBndGZzX2RhdGFfc3ByaW5nXzIwMjQkcm91dGVzDQp0cmlwcyA8LSBndGZzX2RhdGFfc3ByaW5nXzIwMjQkdHJpcHMNCnN0b3BfdGltZXMgPC0gZ3Rmc19kYXRhX3NwcmluZ18yMDI0JHN0b3BfdGltZXMNCmNhbGVuZGFyX2RhdGVzIDwtIGd0ZnNfZGF0YV9zcHJpbmdfMjAyNCRjYWxlbmRhcl9kYXRlcw0Kc3RvcHMgPC0gZ3Rmc19kYXRhX3NwcmluZ18yMDI0JHN0b3BzDQpgYGANCkZpbHRlciBSb3V0ZSAxMDoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUUsIHJlc3VsdHMgPSAnaGlkZSd9DQp0cmlwc19kYXRhIDwtIHRyaXBzICU+JQ0KICBmaWx0ZXIocm91dGVfaWQgPT0gIjEwIiAmICF0cmlwX2hlYWRzaWduID09ICI0MHRoLU1hcmtldCIpICAgDQpgYGANCkdldCB1bmlxdWUgZGlyZWN0aW9ucyBmb3IgdGhlIHJvdXRlLCBleGNsdWRpbmcgIjQwdGgtTWFya2V0Ig0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGU9VFJVRSwgcmVzdWx0cyA9ICdoaWRlJ30NCmRpcmVjdGlvbnMgPC0gdHJpcHNfZGF0YSAlPiUNCiAgICBmaWx0ZXIodHJpcF9oZWFkc2lnbiAhPSAiNDB0aC1NYXJrZXQiKSAlPiUNCiAgICBwdWxsKHRyaXBfaGVhZHNpZ24pICU+JQ0KICAgIHVuaXF1ZSgpDQpgYGANCkZpbHRlciB0cmlwcyBpbiBvbmUgZGlyZWN0aW9uIGJ5IGNob29zaW5nIG9uZSBUcmlwIEhlYWRzaWduDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBjYWNoZT1UUlVFLCByZXN1bHRzID0gJ2hpZGUnfQ0KdHJpcHNfZGlyZWN0aW9uIDwtIHRyaXBzX2RhdGEgJT4lDQogICAgICBmaWx0ZXIodHJpcF9oZWFkc2lnbiA9PSAiMTN0aC1NYXJrZXQiKQ0KYGBgDQpGaWx0ZXIgU2VydmljZSBJRHMgZm9yIHdlZWtkYXkgdHJpcHMNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUUsIHJlc3VsdHMgPSAnaGlkZSd9DQp3ZWVrZGF5X3NlcnZpY2VfaWRzIDwtIGNhbGVuZGFyX2RhdGVzICU+JQ0KICBtdXRhdGUod2Vla2RheSA9IHdkYXkoZGF0ZSwgbGFiZWwgPSBUUlVFKSkgJT4lICAjIEFkZCBhIGNvbHVtbiBmb3IgdGhlIGRheSBvZiB0aGUgd2Vlaw0KICBmaWx0ZXIoIXdlZWtkYXkgJWluJSBjKCJTdW4iLCAiU2F0IikpICU+JSAgICAgICAjIEV4Y2x1ZGUgd2Vla2VuZHMNCiAgcHVsbChzZXJ2aWNlX2lkKSANCmBgYA0KRmluZCBjb21tb24gU2VydmljZSBJRHMgdGhhdCBleGlzdHMgaW4gYm90aCB0cmlwc19kYXRhIGFuZCBjYWxlbmRhcl9kYXRlcw0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGU9VFJVRSwgcmVzdWx0cyA9ICdoaWRlJ30NCmNvbW1vbl9zZXJ2aWNlX2lkcyA8LSB3ZWVrZGF5X3NlcnZpY2VfaWRzW3dlZWtkYXlfc2VydmljZV9pZHMgJWluJSB0cmlwc19kYXRhJHNlcnZpY2VfaWRdDQpgYGANCkZpbHRlciBjYWxlbmRhciBkYXRlcyB3aXRoIG9ubHkgdGhlIGNvbW1vbiBTZXJ2aWNlIElEcyBhbmQgcmVtb3ZlIGRhdGVzIHdoZXJlIHNlcnZpY2VzIHdlcmUgZXhjbHVkZWQgKGV4Y2VwdGlvbl90eXBlID0gMikNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUUsIHJlc3VsdHMgPSAnaGlkZSd9DQpmaWx0ZXJlZF9jYWxlbmRhcl9kYXRlcyA8LSBjYWxlbmRhcl9kYXRlcyAlPiUNCiAgICAgIGZpbHRlcihzZXJ2aWNlX2lkICVpbiUgY29tbW9uX3NlcnZpY2VfaWRzKQ0KYGBgDQpKb2luIHRoZSBmaWx0ZXJlZCBjYWxlbmRhciBkYXRlcyB3aXRoIHRyaXBzIG9uIHRoZSBzZWxlY3RlZCByb3V0ZSBhbmQgZGlyZWN0aW9uDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBjYWNoZT1UUlVFLCByZXN1bHRzID0gJ2hpZGUnfQ0KdHJpcHNfZGF0ZXMgPC0gcmlnaHRfam9pbih0cmlwc19kaXJlY3Rpb24sIGZpbHRlcmVkX2NhbGVuZGFyX2RhdGVzLCBieSA9ICJzZXJ2aWNlX2lkIikgJT4lDQogICAgICBmaWx0ZXIoIWlzLm5hKHRyaXBfaWQpKQ0KYGBgDQpBcnJhbmdlIFNlcnZpY2UgSURzIGJ5IG1vc3QgdG8gbGVhc3QgcnVuIHBhdHRlcm5zIGFjcm9zcyB0aGUgZmlsdGVyZWQgY2FsZW5kYXIgZGF0ZXMNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUUsIHJlc3VsdHMgPSAnaGlkZSd9DQp0cmlwc19wZXJfc2VydmljZV9pZCA8LSB0cmlwc19kYXRlcyAlPiUNCiAgICAgIGdyb3VwX2J5KGRhdGUsIHNlcnZpY2VfaWQpICU+JQ0KICAgICAgY291bnQoZGF0ZSkgJT4lDQogICAgICBhcnJhbmdlKGRlc2MobikpDQpgYGANCkV4dHJhY3QgdGhlIFNlcnZpY2UgSUQgd2l0aCB0aGUgbW9zdCBwYXR0ZXJucw0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGU9VFJVRSwgcmVzdWx0cyA9ICdoaWRlJ30NCmJlc3Rfc2VydmljZV9pZCA8LSB0cmlwc19wZXJfc2VydmljZV9pZFsxLF0gJT4lICAgIyBTb3J0IGJ5IGNvdW50IGluIGRlc2NlbmRpbmcgb3JkZXINCiAgICAgIHB1bGwoc2VydmljZV9pZCkNCmBgYA0KRmlsdGVyIGFsbCB0cmlwcyB0aGF0IHJ1biBvbiB0aGUgc2VsZWN0ZWQgU2VydmljZSBJRA0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGU9VFJVRSwgcmVzdWx0cyA9ICdoaWRlJ30NCnRyaXBzX3NlbGVjdGVkIDwtIHRyaXBzX2RpcmVjdGlvbiAlPiUNCiAgICBmaWx0ZXIoc2VydmljZV9pZCA9PSBiZXN0X3NlcnZpY2VfaWQpDQpgYGANCkZpbHRlciB0aGUgc3RvcCB0aW1lcyBjb3JyZXNwb25kaW5nIHRvIHRoZSBzZWxlY3RlZCBwYXR0ZXJucyBhbmQgY29udmVydCB0aW1lIHZhcmlhYmxlcyB0byBtb3JlIHVzYWJsZSBmb3JtYXRzDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBjYWNoZT1UUlVFLCByZXN1bHRzID0gJ2hpZGUnfQ0KdHJpcF9zdG9wX3RpbWVzIDwtIHN0b3BfdGltZXMgJT4lDQogICAgZmlsdGVyKHRyaXBfaWQgJWluJSB0cmlwc19zZWxlY3RlZCR0cmlwX2lkKSAlPiUNCiAgICBtdXRhdGUoDQogICAgICBkZXBhcnR1cmVfdGltZSA9IGFzLmNoYXJhY3RlcihkZXBhcnR1cmVfdGltZSksDQogICAgICBkZXBhcnR1cmVfc2Vjb25kcyA9IHNhcHBseShkZXBhcnR1cmVfdGltZSwgdGltZV90b19zZWNvbmRzKSwNCiAgICAgIGRlcGFydHVyZV9ob3VycyA9IGRlcGFydHVyZV9zZWNvbmRzIC8gMzYwMCwNCiAgICAgIHRpbWVfcGVyaW9kcyA9IHRpbWVfcGVyaW9kcyhkZXBhcnR1cmVfaG91cnMpLA0KICAgICAgYXJyaXZhbF90aW1lID0gYXMuUE9TSVhjdChhcnJpdmFsX3RpbWUsIGZvcm1hdCA9ICIlSDolTTolUyIsIHR6ID0gIlVUQyIpDQogICAgKSAlPiUNCiAgICBhcnJhbmdlKHRyaXBfaWQsIHN0b3Bfc2VxdWVuY2UpDQpgYGANCiMjIyBQbG90IHRoZSBTdHJpbmcgTGluZSBDaGFydCBmb3Igb25lIHJvdXRlIGFuZCBkaXJlY3Rpb24NCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUUsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD02fQ0KZ2dwbG90KHRyaXBfc3RvcF90aW1lcywgYWVzKHggPSBhcnJpdmFsX3RpbWUsIHkgPSBzdG9wX3NlcXVlbmNlLCBjb2xvciA9IHRpbWVfcGVyaW9kcywgZ3JvdXAgPSB0cmlwX2lkKSkgKw0KICAgICAgICBnZW9tX2xpbmUoKSArDQogICAgICAgIGxhYnMoDQogICAgICAgICAgdGl0bGUgPSBwYXN0ZSgiU3RyaW5nIExpbmUgQ2hhcnQgZm9yIFJvdXRlIiwgIjEwIiwgIjEzdGgtTWFya2V0IiwgIm9uIFdlZWtkYXlzIiwgImluIFNwcmluZyAyMDI0IiksDQogICAgICAgICAgeCA9ICJUaW1lIiwNCiAgICAgICAgICB5ID0gIlN0b3AgU2VxdWVuY2UiDQogICAgICAgICkgKw0KICAgICAgICBzY2FsZV94X2RhdGV0aW1lKGRhdGVfbGFiZWxzID0gIiVIOiVNIikgKw0KICAgICAgICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQojIyMgQ3JlYXRlIGEgZnVuY3Rpb24gdG8gY3JlYXRlIHN0cmluZyBsaW5lcyBmb3IgZWFjaCB0cm9sbGV5IHJvdXRlIGFuZCBkaXJlY3Rpb24gZm9yIGEgc2luZ2xlIHNlYXNvbi4NCg0KTWV0aG9kb2xvZ3k6IFJ1biBmb3IgbG9vcHMgdGhhdCBjeWNsZSB0aHJvdWdoIGVhY2ggUm91dGUgYW5kIERpcmVjdGlvbg0KDQpgYGB7cn0NCmNyZWF0ZV9zdHJpbmdfbGluZV9jaGFydCA8LSBmdW5jdGlvbihndGZzX2RhdGFfc3ByaW5nXzIwMjQsIHJvdXRlc19saXN0KSB7DQogIHJvdXRlcyA8LSBndGZzX2RhdGFfc3ByaW5nXzIwMjQkcm91dGVzDQogIHRyaXBzIDwtIGd0ZnNfZGF0YV9zcHJpbmdfMjAyNCR0cmlwcw0KICBzdG9wX3RpbWVzIDwtIGd0ZnNfZGF0YV9zcHJpbmdfMjAyNCRzdG9wX3RpbWVzDQogIGNhbGVuZGFyX2RhdGVzIDwtIGd0ZnNfZGF0YV9zcHJpbmdfMjAyNCRjYWxlbmRhcl9kYXRlcw0KICBzdG9wcyA8LSBndGZzX2RhdGFfc3ByaW5nXzIwMjQkc3RvcHMNCiAgDQogIGZvciAocm91dGUgaW4gcm91dGVzX2xpc3QpIHsNCiAgICAjIEZpbHRlciB0aGUgcm91dGVzIHRhYmxlIHRvIGZpbmQgdGhlIGNvcnJlY3Qgcm91dGVfaWQNCiAgICByb3V0ZV9kYXRhIDwtIHJvdXRlcyAlPiUNCiAgICAgIGZpbHRlcihyb3V0ZV9pZCA9PSByb3V0ZSkNCiAgICANCiAgICBpZiAobnJvdyhyb3V0ZV9kYXRhKSA9PSAwKSB7DQogICAgICBtZXNzYWdlKHBhc3RlKCJSb3V0ZSIsIHJvdXRlLCAibm90IGZvdW5kIGluIEdURlMgZGF0YS4gU2tpcHBpbmcuLi4iKSkNCiAgICAgIG5leHQNCiAgICB9DQogICAgDQogICAgdHJpcHNfZGF0YSA8LSB0cmlwcyAlPiUNCiAgICAgIGZpbHRlcihyb3V0ZV9pZCA9PSByb3V0ZV9kYXRhJHJvdXRlX2lkKQ0KICAgIA0KICAgICMgR2V0IHVuaXF1ZSBkaXJlY3Rpb25zIGZvciB0aGUgcm91dGUsIGV4Y2x1ZGluZyAiNDB0aC1NYXJrZXQiDQogICAgZGlyZWN0aW9ucyA8LSB0cmlwc19kYXRhICU+JQ0KICAgICAgZmlsdGVyKHRyaXBfaGVhZHNpZ24gIT0gIjQwdGgtTWFya2V0IikgJT4lDQogICAgICBwdWxsKHRyaXBfaGVhZHNpZ24pICU+JQ0KICAgICAgdW5pcXVlKCkNCiAgICANCiAgICBmb3IgKGRpcmVjdGlvbiBpbiBkaXJlY3Rpb25zKSB7DQogICAgICB0cmlwc19kaXJlY3Rpb24gPC0gdHJpcHNfZGF0YSAlPiUNCiAgICAgICAgZmlsdGVyKHRyaXBfaGVhZHNpZ24gPT0gZGlyZWN0aW9uKQ0KICAgICAgDQogICAgICB3ZWVrZGF5X3NlcnZpY2VfaWRzIDwtIGNhbGVuZGFyX2RhdGVzICU+JQ0KICAgICAgICBtdXRhdGUod2Vla2RheSA9IHdkYXkoZGF0ZSwgbGFiZWwgPSBUUlVFKSkgJT4lICAjIEFkZCBhIGNvbHVtbiBmb3IgdGhlIGRheSBvZiB0aGUgd2Vlaw0KICAgICAgICBmaWx0ZXIoIXdlZWtkYXkgJWluJSBjKCJTdW4iLCAiU2F0IikpICU+JSAgICAgICAjIEV4Y2x1ZGUgd2Vla2VuZHMNCiAgICAgICAgcHVsbChzZXJ2aWNlX2lkKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBFeHRyYWN0IHRoZSBzZXJ2aWNlX2lkIHZhbHVlcw0KICAgICAgDQogICAgICAjIEZpbmQgY29tbW9uIHNlcnZpY2VfaWRzIHRoYXQgZXhpc3RzIGluIGJvdGggdHJpcHNfZGF0YSBhbmQgY2FsZW5kYXJfZGF0ZXMNCiAgICAgIGNvbW1vbl9zZXJ2aWNlX2lkcyA8LSB3ZWVrZGF5X3NlcnZpY2VfaWRzW3dlZWtkYXlfc2VydmljZV9pZHMgJWluJSB0cmlwc19kaXJlY3Rpb24kc2VydmljZV9pZF0NCiAgICAgIA0KICAgICAgaWYgKGxlbmd0aChjb21tb25fc2VydmljZV9pZHMpID09IDApIHsNCiAgICAgICAgbWVzc2FnZShwYXN0ZSgiTm8gY29tbW9uIHNlcnZpY2VfaWQgZm91bmQgZm9yIHJvdXRlIiwgcm91dGUsICJhbmQgZGlyZWN0aW9uIiwgZGlyZWN0aW9uLCAiLiBTa2lwcGluZy4uLiIpKQ0KICAgICAgICBuZXh0DQogICAgICB9DQogICAgICANCiAgICAgIGZpbHRlcmVkX2NhbGVuZGFyX2RhdGVzIDwtIGNhbGVuZGFyX2RhdGVzICU+JQ0KICAgICAgICBmaWx0ZXIoc2VydmljZV9pZCAlaW4lIGNvbW1vbl9zZXJ2aWNlX2lkcykNCiAgICAgIA0KICAgICAgdHJpcHNfZGF0ZXMgPC0gcmlnaHRfam9pbih0cmlwc19kaXJlY3Rpb24sIGZpbHRlcmVkX2NhbGVuZGFyX2RhdGVzLCBieSA9ICJzZXJ2aWNlX2lkIikgJT4lDQogICAgICAgIGZpbHRlcighaXMubmEodHJpcF9pZCkpDQogICAgICANCiAgICAgIHRyaXBzX3Blcl9zZXJ2aWNlX2lkIDwtIHRyaXBzX2RhdGVzICU+JQ0KICAgICAgICBncm91cF9ieShkYXRlLCBzZXJ2aWNlX2lkKSAlPiUNCiAgICAgICAgY291bnQoZGF0ZSkgJT4lDQogICAgICAgIGFycmFuZ2UoZGVzYyhuKSkNCiAgICAgIA0KICAgICAgYmVzdF9zZXJ2aWNlX2lkIDwtIHRyaXBzX3Blcl9zZXJ2aWNlX2lkWzEsIF0gJT4lDQogICAgICAgIHB1bGwoc2VydmljZV9pZCkNCiAgICAgIA0KICAgICAgdHJpcHNfb25fZGF0ZSA8LSB0cmlwc19kaXJlY3Rpb24gJT4lDQogICAgICAgIGZpbHRlcihzZXJ2aWNlX2lkID09IGJlc3Rfc2VydmljZV9pZCkNCiAgICAgIA0KICAgICAgdHJpcF9zdG9wX3RpbWVzIDwtIHN0b3BfdGltZXMgJT4lDQogICAgICAgIGZpbHRlcih0cmlwX2lkICVpbiUgdHJpcHNfb25fZGF0ZSR0cmlwX2lkKSAlPiUNCiAgICAgICAgbXV0YXRlKA0KICAgICAgICAgIGRlcGFydHVyZV90aW1lID0gYXMuY2hhcmFjdGVyKGRlcGFydHVyZV90aW1lKSwNCiAgICAgICAgICBkZXBhcnR1cmVfc2Vjb25kcyA9IHNhcHBseShkZXBhcnR1cmVfdGltZSwgdGltZV90b19zZWNvbmRzKSwNCiAgICAgICAgICBkZXBhcnR1cmVfaG91cnMgPSBkZXBhcnR1cmVfc2Vjb25kcyAvIDM2MDAsDQogICAgICAgICAgdGltZV9wZXJpb2RzID0gdGltZV9wZXJpb2RzKGRlcGFydHVyZV9ob3VycyksDQogICAgICAgICAgYXJyaXZhbF90aW1lID0gYXMuUE9TSVhjdChhcnJpdmFsX3RpbWUsIGZvcm1hdCA9ICIlSDolTTolUyIsIHR6ID0gIlVUQyIpDQogICAgICAgICkgJT4lDQogICAgICAgIGFycmFuZ2UodHJpcF9pZCwgc3RvcF9zZXF1ZW5jZSkNCiAgICAgIA0KICAgICAgdHJpcF9zdG9wX3RpbWVzX3dpdGhfbmFtZXMgPC0gdHJpcF9zdG9wX3RpbWVzICU+JQ0KICAgICAgICBsZWZ0X2pvaW4oc3RvcHMsIGJ5ID0gInN0b3BfaWQiKSAlPiUNCiAgICAgICAgbXV0YXRlKHN0b3BfbmFtZV9udW1iZXIgPSBwYXN0ZShzdG9wX3NlcXVlbmNlLCBzdG9wX25hbWUsIHNlcCA9ICJfIikpDQogICAgICANCiAgICAgIHN0cmluZ19jaGFydCA8LSBnZ3Bsb3QodHJpcF9zdG9wX3RpbWVzX3dpdGhfbmFtZXMsIGFlcyh4ID0gYXJyaXZhbF90aW1lLCB5ID0gc3RvcF9zZXF1ZW5jZSwgY29sb3IgPSB0aW1lX3BlcmlvZHMsIGdyb3VwID0gdHJpcF9pZCkpICsNCiAgICAgICAgZ2VvbV9saW5lKCkgKw0KICAgICAgICBsYWJzKA0KICAgICAgICAgIHRpdGxlID0gcGFzdGUoIlN0cmluZyBMaW5lIENoYXJ0IGZvciBSb3V0ZSIsIHJvdXRlLCAiIFRyaXBzIHRvIiwgZGlyZWN0aW9uLCAib24gV2Vla2RheXMiLCAiaW4gU3ByaW5nIDIwMjQiKSwNCiAgICAgICAgICB4ID0gIlRpbWUiLA0KICAgICAgICAgIHkgPSAiU3RvcCBTZXF1ZW5jZSINCiAgICAgICAgKSArDQogICAgICAgIHNjYWxlX3hfZGF0ZXRpbWUoZGF0ZV9sYWJlbHMgPSAiJUg6JU0iKSArDQogICAgICAgIHRoZW1lX21pbmltYWwoKQ0KICAgICBvdXRwdXRfcGF0aCA8LSBoZXJlKCJTdHJpbmcgTGluZXMgRXhwb3J0cyIsIHBhc3RlMCgiZ3Rmc19yb3V0ZV8iLCByb3V0ZSwgIl8iLCBnc3ViKCIgIiwgIl8iLCBkaXJlY3Rpb24pLCAiX1dlZWtkYXlfc3RyaW5nX2xpbmVfY2hhcnQuanBnIikpDQogICAgICBnZ3NhdmUob3V0cHV0X3BhdGgsIHBsb3QgPSBzdHJpbmdfY2hhcnQsIHdpZHRoID0gNDAwMCwgaGVpZ2h0ID0gMTc1MCwgdW5pdHMgPSAicHgiKQ0KICAgICAgcHJpbnQoc3RyaW5nX2NoYXJ0KQ0KICAgIH0NCiAgfQ0KfQ0KYGBgDQoNCg0KIyMjIFJ1biB0aGUgZnVuY3Rpb24gdG8gY3JlYXRlIHN0cmluZyBsaW5lcyBmb3Igcm91dGVzIDEwLCAxMSwgMTMsIDE1LCAzNCwgMzYNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGU9VFJVRSwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTQuNX0NCmNyZWF0ZV9zdHJpbmdfbGluZV9jaGFydChndGZzX2RhdGFfc3ByaW5nXzIwMjQsIGMoIjEwIiwgIjExIiwgIjEzIiwgIjE1IiwgIjM0IiwgIjM2IikpDQpgYGANCg0KIyMjIEV4cG9ydCBzdHJpbmcgbGluZXMgZm9yIGFsbCBhdmFpbGFibGUgc2Vhc29uczoNCg0KTWV0aG9kb2xvZ3k6IFJ1biBmb3IgbG9vcHMgdGhhdCBjeWNsZSB0aHJvdWdoIGVhY2ggUm91dGUsIERpcmVjdGlvbiBhbmQgU2Vhc29uDQoNCg0KQ3JlYXRlIGEgZnVuY3Rpb24gdG8gY3JlYXRlIHN0cmluZyBsaW5lcyBmb3IgYWxsIGF2YWlsYWJsZSBzZWFzb25zOg0KYGBge3J9DQpjcmVhdGVfc3RyaW5nX2xpbmVfY2hhcnRfc2Vhc29ucyA8LSBmdW5jdGlvbihndGZzX2RhdGEsIHJvdXRlc19saXN0LCBzZWFzb24pIHsNCiAgcm91dGVzIDwtIGd0ZnNfZGF0YSRyb3V0ZXMNCiAgdHJpcHMgPC0gZ3Rmc19kYXRhJHRyaXBzDQogIHN0b3BfdGltZXMgPC0gZ3Rmc19kYXRhJHN0b3BfdGltZXMNCiAgY2FsZW5kYXJfZGF0ZXMgPC0gZ3Rmc19kYXRhJGNhbGVuZGFyX2RhdGVzDQogIHN0b3BzIDwtIGd0ZnNfZGF0YSRzdG9wcw0KICANCiAgZm9yIChyb3V0ZSBpbiByb3V0ZXNfbGlzdCkgew0KICAgICMgRmlsdGVyIHRoZSByb3V0ZXMgdGFibGUgdG8gZmluZCB0aGUgY29ycmVjdCByb3V0ZV9pZA0KICAgIHJvdXRlX2RhdGEgPC0gcm91dGVzICU+JQ0KICAgICAgZmlsdGVyKHJvdXRlX2lkID09IHJvdXRlKQ0KICAgIA0KICAgIGlmIChucm93KHJvdXRlX2RhdGEpID09IDApIHsNCiAgICAgIG1lc3NhZ2UocGFzdGUoIlJvdXRlIiwgcm91dGUsICJub3QgZm91bmQgaW4gR1RGUyBkYXRhLiBTa2lwcGluZy4uLiIpKQ0KICAgICAgbmV4dA0KICAgIH0NCiAgICANCiAgICB0cmlwc19kYXRhIDwtIHRyaXBzICU+JQ0KICAgICAgZmlsdGVyKHJvdXRlX2lkID09IHJvdXRlX2RhdGEkcm91dGVfaWQpDQogICAgDQogICAgIyBHZXQgdW5pcXVlIGRpcmVjdGlvbnMgZm9yIHRoZSByb3V0ZSwgZXhjbHVkaW5nICI0MHRoLU1hcmtldCINCiAgICBkaXJlY3Rpb25zIDwtIHRyaXBzX2RhdGEgJT4lDQogICAgICBmaWx0ZXIodHJpcF9oZWFkc2lnbiAhPSAiNDB0aC1NYXJrZXQiKSAlPiUNCiAgICAgIHB1bGwodHJpcF9oZWFkc2lnbikgJT4lDQogICAgICB1bmlxdWUoKQ0KICAgIA0KICAgIGZvciAoZGlyZWN0aW9uIGluIGRpcmVjdGlvbnMpIHsNCiAgICAgIHRyaXBzX2RpcmVjdGlvbiA8LSB0cmlwc19kYXRhICU+JQ0KICAgICAgICBmaWx0ZXIodHJpcF9oZWFkc2lnbiA9PSBkaXJlY3Rpb24pDQogICAgICANCiAgICAgIHdlZWtkYXlfc2VydmljZV9pZHMgPC0gY2FsZW5kYXJfZGF0ZXMgJT4lDQogICAgICAgIG11dGF0ZSh3ZWVrZGF5ID0gd2RheShkYXRlLCBsYWJlbCA9IFRSVUUpKSAlPiUgICMgQWRkIGEgY29sdW1uIGZvciB0aGUgZGF5IG9mIHRoZSB3ZWVrDQogICAgICAgIGZpbHRlcighd2Vla2RheSAlaW4lIGMoIlN1biIsICJTYXQiKSkgJT4lICAgICAgICMgRXhjbHVkZSB3ZWVrZW5kcw0KICAgICAgICBwdWxsKHNlcnZpY2VfaWQpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEV4dHJhY3QgdGhlIHNlcnZpY2VfaWQgdmFsdWVzDQogICAgICANCiAgICAgICMgRmluZCBjb21tb24gc2VydmljZV9pZHMgdGhhdCBleGlzdCBpbiBib3RoIHRyaXBzX2RhdGEgYW5kIGNhbGVuZGFyX2RhdGVzDQogICAgICBjb21tb25fc2VydmljZV9pZHMgPC0gd2Vla2RheV9zZXJ2aWNlX2lkc1t3ZWVrZGF5X3NlcnZpY2VfaWRzICVpbiUgdHJpcHNfZGlyZWN0aW9uJHNlcnZpY2VfaWRdDQogICAgICANCiAgICAgIGlmIChsZW5ndGgoY29tbW9uX3NlcnZpY2VfaWRzKSA9PSAwKSB7DQogICAgICAgIG1lc3NhZ2UocGFzdGUoIk5vIGNvbW1vbiBzZXJ2aWNlX2lkIGZvdW5kIGZvciByb3V0ZSIsIHJvdXRlLCAiYW5kIGRpcmVjdGlvbiIsIGRpcmVjdGlvbiwgIi4gU2tpcHBpbmcuLi4iKSkNCiAgICAgICAgbmV4dA0KICAgICAgfQ0KICAgICAgDQogICAgICBmaWx0ZXJlZF9jYWxlbmRhcl9kYXRlcyA8LSBjYWxlbmRhcl9kYXRlcyAlPiUNCiAgICAgICAgZmlsdGVyKHNlcnZpY2VfaWQgJWluJSBjb21tb25fc2VydmljZV9pZHMpDQogICAgICANCiAgICAgIHRyaXBzX2RhdGVzIDwtIHJpZ2h0X2pvaW4odHJpcHNfZGlyZWN0aW9uLCBmaWx0ZXJlZF9jYWxlbmRhcl9kYXRlcywgYnkgPSAic2VydmljZV9pZCIpICU+JQ0KICAgICAgICBmaWx0ZXIoIWlzLm5hKHRyaXBfaWQpKQ0KICAgICAgDQogICAgICB0cmlwc19wZXJfc2VydmljZV9pZCA8LSB0cmlwc19kYXRlcyAlPiUNCiAgICAgICAgZ3JvdXBfYnkoZGF0ZSwgc2VydmljZV9pZCkgJT4lDQogICAgICAgIGNvdW50KGRhdGUpICU+JQ0KICAgICAgICBhcnJhbmdlKGRlc2MobikpDQogICAgICANCiAgICAgIGJlc3Rfc2VydmljZV9pZCA8LSB0cmlwc19wZXJfc2VydmljZV9pZFsxLCBdICU+JQ0KICAgICAgICBwdWxsKHNlcnZpY2VfaWQpDQogICAgICANCiAgICAgIHRyaXBzX29uX2RhdGUgPC0gdHJpcHNfZGlyZWN0aW9uICU+JQ0KICAgICAgICBmaWx0ZXIoc2VydmljZV9pZCA9PSBiZXN0X3NlcnZpY2VfaWQpDQogICAgICANCiAgICAgIHRyaXBfc3RvcF90aW1lcyA8LSBzdG9wX3RpbWVzICU+JQ0KICAgICAgICBmaWx0ZXIodHJpcF9pZCAlaW4lIHRyaXBzX29uX2RhdGUkdHJpcF9pZCkgJT4lDQogICAgICAgIG11dGF0ZSgNCiAgICAgICAgICBkZXBhcnR1cmVfdGltZSA9IGFzLmNoYXJhY3RlcihkZXBhcnR1cmVfdGltZSksDQogICAgICAgICAgZGVwYXJ0dXJlX3NlY29uZHMgPSBzYXBwbHkoZGVwYXJ0dXJlX3RpbWUsIHRpbWVfdG9fc2Vjb25kcyksDQogICAgICAgICAgZGVwYXJ0dXJlX2hvdXJzID0gZGVwYXJ0dXJlX3NlY29uZHMgLyAzNjAwLA0KICAgICAgICAgIHRpbWVfcGVyaW9kcyA9IHRpbWVfcGVyaW9kcyhkZXBhcnR1cmVfaG91cnMpLA0KICAgICAgICAgIGFycml2YWxfdGltZSA9IGFzLlBPU0lYY3QoYXJyaXZhbF90aW1lLCBmb3JtYXQgPSAiJUg6JU06JVMiLCB0eiA9ICJVVEMiKQ0KICAgICAgICApICU+JQ0KICAgICAgICBhcnJhbmdlKHRyaXBfaWQsIHN0b3Bfc2VxdWVuY2UpDQogICAgICANCiAgICAgIHRyaXBfc3RvcF90aW1lc193aXRoX25hbWVzIDwtIHRyaXBfc3RvcF90aW1lcyAlPiUNCiAgICAgICAgbGVmdF9qb2luKHN0b3BzLCBieSA9ICJzdG9wX2lkIikgJT4lDQogICAgICAgIG11dGF0ZShzdG9wX25hbWVfbnVtYmVyID0gcGFzdGUoc3RvcF9zZXF1ZW5jZSwgc3RvcF9uYW1lLCBzZXAgPSAiXyIpKQ0KICAgICAgDQogICAgICBzdHJpbmdfY2hhcnQgPC0gZ2dwbG90KHRyaXBfc3RvcF90aW1lc193aXRoX25hbWVzLCBhZXMoeCA9IGFycml2YWxfdGltZSwgeSA9IHN0b3Bfc2VxdWVuY2UsIGNvbG9yID0gdGltZV9wZXJpb2RzLCBncm91cCA9IHRyaXBfaWQpKSArDQogICAgICAgIGdlb21fbGluZSgpICsNCiAgICAgICAgbGFicygNCiAgICAgICAgICB0aXRsZSA9IHBhc3RlKCJTdHJpbmcgTGluZSBDaGFydCBmb3IgUm91dGUiLCByb3V0ZSwgIlRyaXBzIHRvIiwgZGlyZWN0aW9uLCAib24gV2Vla2RheXMiLCBzZWFzb24pLA0KICAgICAgICAgIHggPSAiVGltZSIsDQogICAgICAgICAgeSA9ICJTdG9wIFNlcXVlbmNlIg0KICAgICAgICApICsNCiAgICAgICAgc2NhbGVfeF9kYXRldGltZShkYXRlX2xhYmVscyA9ICIlSDolTSIpICsNCiAgICAgICAgdGhlbWVfbWluaW1hbCgpDQogICAgICANCiAgICAgICMgQ3JlYXRlIHRoZSBzZWFzb24tc3BlY2lmaWMgZm9sZGVyIGluc2lkZSAiU3RyaW5nIExpbmVzIEV4cG9ydHMiDQogICAgICBzZWFzb25fZm9sZGVyIDwtIGhlcmUoIlN0cmluZyBMaW5lcyBFeHBvcnRzIiwgc2Vhc29uKQ0KICAgICAgaWYgKCFkaXIuZXhpc3RzKHNlYXNvbl9mb2xkZXIpKSB7DQogICAgICAgIGRpci5jcmVhdGUoc2Vhc29uX2ZvbGRlciwgcmVjdXJzaXZlID0gVFJVRSkNCiAgICAgIH0NCiAgICAgIA0KICAgICAgb3V0cHV0X3BhdGggPC0gZmlsZS5wYXRoKHNlYXNvbl9mb2xkZXIsIHBhc3RlMCgiZ3Rmc19yb3V0ZV8iLCByb3V0ZSwgIl8iLCBnc3ViKCIgIiwgIl8iLCBkaXJlY3Rpb24pLCAiX1dlZWtkYXlfc3RyaW5nX2xpbmVfY2hhcnQuanBnIikpDQogICAgICBnZ3NhdmUob3V0cHV0X3BhdGgsIHBsb3QgPSBzdHJpbmdfY2hhcnQsIHdpZHRoID0gNDAwMCwgaGVpZ2h0ID0gMTc1MCwgdW5pdHMgPSAicHgiKQ0KICAgICAgcHJpbnQoc3RyaW5nX2NoYXJ0KQ0KICAgIH0NCiAgfQ0KfQ0KDQpgYGANCg0KTG9vcCBvdmVyIGVhY2ggc2Vhc29uIGFuZCBjcmVhdGUgc3RyaW5nIGxpbmUgY2hhcnRzIGZvciBlYWNoIHJvdXRlLCBhbmQgc2F2ZSBpbiB0aGVpciByZXNwZWN0aXZlIGZvbGRlcnM6DQoNCmBgYHtyfQ0KDQpmb3IgKHNlYXNvbiBpbiBzZWFzb25fbGlzdCRzZWFzb25fbmFtZSkgew0KICBndGZzX2ZpbGUgPC0gaGVyZSgiR1RGUyBEYXRhIiwgcGFzdGUwKCJnb29nbGVfYnVzXyIsIHNlYXNvbiwgIi56aXAiKSkNCiAgDQogIGlmICghZmlsZS5leGlzdHMoZ3Rmc19maWxlKSkgew0KICAgIG1lc3NhZ2UocGFzdGUoIkdURlMgZmlsZSBmb3IiLCBzZWFzb24sICJub3QgZm91bmQuIFNraXBwaW5nLi4uIikpDQogICAgbmV4dA0KICB9DQogIA0KICBndGZzX2RhdGEgPC0gcmVhZF9ndGZzKGd0ZnNfZmlsZSkNCiAgDQogIGNyZWF0ZV9zdHJpbmdfbGluZV9jaGFydF9zZWFzb25zKGd0ZnNfZGF0YSwgcm91dGVzX2xpc3QsIHNlYXNvbikNCn0NCmBgYA0KDQo=